﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Growatt.OutPutDataProcess;

namespace Growatt.WaveData.DataSetResolve
{
    public class VariableInfo
    {
        public string VariableName { get; set; }
        public string Address { get; set; }
        public string SzieVar { get; set; }

        public VariableInfo(string variableName, string address)
        {
            VariableName = variableName;
            Address = address;
        }
    }

    public class DwarfParser
    {
        private readonly Dictionary<string, string> Map_TypePrefix = new Dictionary<string, string>
        {
            { "DW_TAG_base_type", "" },
            { "DW_TAG_structure_type", "struct " },
            { "DW_TAG_union_type", "union " },
            { "DW_TAG_pointer_type", "pointer " }
        };

        private readonly Dictionary<string, string> Map_AnonTypes = new Dictionary<string, string>
        {
            { "DW_TAG_subroutine_type", "subroutine" },
            { "DW_TAG_pointer_type", "pointer" },
            { "DW_TAG_union_type", "union" }
        };
        public Dictionary<string, Dictionary<string, string>> struct_data = new Dictionary<string, Dictionary<string, string>>();
        public Dictionary<string, Die> dieDictionary = new Dictionary<string, Die>();
        private StreamWriter fileWriter;
        public void ParseDwarfInfo(DwarfInfo dwarfinfo)
        {
            fileWriter = new StreamWriter("dwarf_info_output.txt");
            int j = 0;
            foreach (var CU in dwarfinfo.iter_CUs)
            {
                j++;

                // Start with the top DIE, the root for this CU's DIE tree
                var top_Die = CU.TopLevelDie;

                // Display DIEs recursively starting with top_DIE
                int i = 0;
                foreach (var child in top_Die.Dies)
                {
                    i++;
                    fileWriter.WriteLine($"Top Die[{j}]->child[{i}]:");
                    DieInfoRec(child, "");
                }
            }
            fileWriter.Close();
        }
        private string DieTypeRec(Die die, Die prev)
        {
            var t = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_type");
            if (t == null)
            {
                string prefix = prev != null && prev.Tag == "DW_TAG_pointer_type" ? "* " : "";

                // got a type
                var nameAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_name");
                if (nameAttr != null)
                {
                    // common named type with prefix
                    string typePrefix;
                    if (Map_TypePrefix.ContainsKey(die.Tag))
                    {
                        typePrefix = Map_TypePrefix[die.Tag];
                    }
                    else
                    {
                        typePrefix = $"unknown: {die.Tag}";
                    }

                    return prefix + typePrefix + nameAttr.Value.StringValue;
                }
                else if (die.Tag == "DW_TAG_structure_type" && prev != null && prev.Tag == "DW_TAG_typedef")
                {
                    // typedef-ed anonymous struct
                    return prefix + "struct " + prev.Attributes.First(a => a.Type == "DW_AT_name").Value.StringValue;
                }
                else
                {
                    // no name types
                    string anonType;
                    if (Map_AnonTypes.ContainsKey(die.Tag))
                    {
                        anonType = Map_AnonTypes[die.Tag];
                    }
                    else
                    {
                        anonType = $"unknown: {die.Tag}";
                    }

                    return prefix + anonType;
                }
            }
            else if (t.Form == "DW_FORM_ref_addr")
            {
                var refValue = t.Value.RefValue.Idref;
                if (dieDictionary.TryGetValue(refValue, out var refDie))
                {
                    return DieTypeRec(refDie, die);
                }
                else
                {
                    fileWriter.WriteLine($"Error: DIE reference {refValue} not found.");
                    return "unknown: DW_TAG_reference";
                }
            }
            return "unknown: DW_TAG_type";
        }
        private void DieInfoRec(Die die, string name)
        {
            if (die.Tag == "DW_TAG_member" && die.Attributes.Any(a => a.Type == "DW_AT_name"))
            {
                var memberNameAttr = die.Attributes.First(a => a.Type == "DW_AT_name");
                string memberName = memberNameAttr.Value.StringValue;
                string memberType = DieTypeRec(die, null);

                var memberOffsetAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_data_member_location");
                if (memberOffsetAttr != null)
                {
                    int memberOffset = Convert.ToInt32(memberOffsetAttr.Value.BlockValue.Split(' ')[1].Replace("0x", ""), 16);
                    fileWriter.WriteLine($"  >type: {memberType} , {memberName},  offset: {memberOffset}");
                }
                else
                {
                    var memberBitSizeAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_bit_size");
                    var memberBitOffsetAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_data_bit_offset");
                    if (memberBitSizeAttr != null && memberBitOffsetAttr != null)
                    {
                        int memberBitSize = Convert.ToInt32(memberBitSizeAttr.Value.ConstValue.Replace("0x", ""), 16);
                        int memberBitOffset = int.Parse(memberBitOffsetAttr.Value.ConstValue);
                        fileWriter.WriteLine($"  > .{memberName}, type: {memberType}, bit_offset: {memberBitOffset}, bit_size: {memberBitSize}");
                    }
                }

                // save to return data
                if (!struct_data.ContainsKey(name))
                {
                    struct_data[name] = new Dictionary<string, string>();
                }
                if (memberType.StartsWith("*"))
                {
                    // pointer member, change to *name -> type
                    struct_data[name]["*" + memberName] = memberType.Substring(1);
                }
                else
                {
                    struct_data[name][memberName] = memberType;
                }
            }

            if (die.Tag == "DW_TAG_structure_type" || die.Tag == "DW_TAG_union_type" || die.Tag == "DW_TAG_enumeration_type")
            {
                var nameAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_name");
                if (nameAttr != null)
                {
                    name = Map_TypePrefix[die.Tag] + nameAttr.Value.StringValue;

                    if (die.Attributes.Any(a => a.Type == "DW_AT_declaration" && Convert.ToInt32(a.Value.ConstValue.Replace("0x", ""), 16) == 1))
                    {
                        fileWriter.WriteLine($"{name}: just a declaration");
                        return;
                    }

                    var sizeAttr = die.Attributes.FirstOrDefault(a => a.Type == "DW_AT_byte_size");
                    if (sizeAttr != null)
                    {
                        int size = Convert.ToInt32(sizeAttr.Value.ConstValue.Replace("0x", ""), 16);
                        fileWriter.WriteLine($"{name}, size:{size}");
                    }

                    // recursion into all children DIE
                    foreach (var child in die.Dies)
                    {
                        DieInfoRec(child, name);
                    }
                }
            }
        }
    }

    public class DwarfParserHelper
    {
        public static void DwarfParserService(DwarfInfo section, Dictionary<string, Die> dieDic)
        {
            var parser = new DwarfParser();
            parser.dieDictionary = dieDic;
            parser.ParseDwarfInfo(section);
        }
    }
}
